﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apparat.ShaderManagement;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
using System.Drawing;
using Assimp;
using Assimp.Configs;


namespace Apparat.Renderables.AssimpMeshInterface
{
    public class AssimpMesh : Renderable
    {
        SlimDX.Direct3D11.Buffer vertexBuffer;
        DataStream vertices;

        int vertexStride;
        int numVertices;
        int vertexBufferSizeInBytes;

        Color4 diffuse = new Color4(1, 0, 1, 0);

        System.Drawing.Color ColorDiffuse = System.Drawing.Color.LimeGreen;
        System.Drawing.Color ColorAmbient = System.Drawing.Color.LimeGreen;
        System.Drawing.Color ColorEmissive = System.Drawing.Color.White;
        System.Drawing.Color ColorReflective = System.Drawing.Color.White;
        System.Drawing.Color ColorSpecular = System.Drawing.Color.White;
        System.Drawing.Color ColorTransparent = System.Drawing.Color.White;

        Assimp.Vector3D[] TextureCoordinates;

        string textureFileName = "";



        //indices
        SlimDX.Direct3D11.Buffer indexBuffer;
        int indexStride = 0;
        int numIndices = 0;
        int indexBufferSizeInBytes = 0;
        DataStream indices;

        enum MeshType
        {
            VerticesOnly,
            VertexAndIndexBuffer,
            VertexNormalIndex,
            VertexNormalTextureCoord
        }

        MeshType meshType;
        Assimp.Mesh Mesh;
        Assimp.Material Material;

        public AssimpMesh(Assimp.Mesh mesh, Assimp.Material material)
        {
            if (mesh.PrimitiveType == PrimitiveType.Triangle)
            {

                try
                {

                    this.Mesh = mesh;
                    this.Material = material;

                    bool materialHasTexture = false;

                    TextureSlot texSl = material.GetTexture(TextureType.Diffuse, 0);

                    if (mesh.GetTextureCoords(0) != null && texSl.FilePath != null)
                        materialHasTexture = true;


                    if (mesh.HasVertices && !mesh.HasFaces)
                    {
                        SetVertexBuffer(mesh.Vertices);
                        meshType = MeshType.VerticesOnly;
                    }
                    else if (mesh.HasVertices && mesh.HasFaces)
                    {
                        if (mesh.HasNormals && !materialHasTexture)
                        {
                            SetVertexBufferAndNormals(mesh.Vertices, mesh.Normals);
                            SetIndexBuffer(mesh.Faces);
                            meshType = MeshType.VertexNormalIndex;
                        }
                        else if (mesh.HasNormals && materialHasTexture)
                        {

                            int tccc = mesh.TextureCoordsChannelCount;
                            SetVertexBufferNormalsAndTexCoords(mesh.Vertices, mesh.Normals, mesh.GetTextureCoords(0));
                            meshType = MeshType.VertexNormalTextureCoord;
                            SetIndexBuffer(mesh.Faces);
                            //TextureSlot texSl = material.GetTexture(TextureType.Diffuse, 0);
                            textureFileName = texSl.FilePath;
                            //TextureCoordinates = mesh.GetTextureCoords(0);
                        }
                        else
                        {
                            SetVertexBuffer(mesh.Vertices);
                            SetIndexBuffer(mesh.Faces);
                            meshType = MeshType.VertexAndIndexBuffer;
                        }
                    }

                    ColorDiffuse = AssimpColor4DtoColor(material.ColorDiffuse);
                    ColorAmbient = AssimpColor4DtoColor(material.ColorAmbient);
                    ColorEmissive = AssimpColor4DtoColor(material.ColorEmissive);
                    ColorReflective = AssimpColor4DtoColor(material.ColorReflective);
                    ColorSpecular = AssimpColor4DtoColor(material.ColorSpecular);
                    ColorTransparent = AssimpColor4DtoColor(material.ColorTransparent);
                }
                catch (Exception ex)
                {
                    System.Windows.Forms.MessageBox.Show(ex.ToString());
                }

            }

        }

        private System.Drawing.Color AssimpColor4DtoColor(Assimp.Color4D aColor)
        {
            byte a = (byte)(aColor.A * 255.0f);
            byte r = (byte)(aColor.R * 255.0f);
            byte g = (byte)(aColor.G * 255.0f);
            byte b = (byte)(aColor.B * 255.0f);
            return Color.FromArgb(a, r, g, b);
        }

        private Vector3 ColorToVector3(System.Drawing.Color color)
        {
            return new Vector3(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f);
        }

        private void SetIndexBuffer(Assimp.Face[] faces)
        {
            numIndices = faces.Length * 3;
            indexStride = Marshal.SizeOf(typeof(short)); // 2 bytes
            indexBufferSizeInBytes = numIndices * indexStride;

            indices = new DataStream(indexBufferSizeInBytes, true, true);

            foreach (Assimp.Face face in faces)
            {
                for (int i = 0; i < face.IndexCount; i++)
                {
                    indices.Write((short)face.Indices[i]);
                }
            }

            indices.Position = 0;

            indexBuffer = new SlimDX.Direct3D11.Buffer(
                DeviceManager.Instance.device,
                indices,
                indexBufferSizeInBytes,
                ResourceUsage.Default,
                BindFlags.IndexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None,
                0);
        }

        private void SetVertexBufferNormalsAndTexCoords(Assimp.Vector3D[] points, Assimp.Vector3D[] normals, Assimp.Vector3D[] texCoords)
        {
            numVertices = points.Length;
            vertexStride = Marshal.SizeOf(typeof(PositionNormalTexturedVertex));
            int SizeOfVertexBufferInBytes = numVertices * vertexStride;

            vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);

            for (int i = 0; i < points.Length; i++)
            {
                Vector3 v = new Vector3(points[i].X, points[i].Y, points[i].Z);
                Vector3 n = new Vector3(normals[i].X, normals[i].Y, normals[i].Z);
                Vector2 t = new Vector2(texCoords[i].X, texCoords[i].Y);
                //Vector3 v = new Vector3(points[i].X, points[i].Z, points[i].Y);
                //Vector3 n = new Vector3(normals[i].X, normals[i].Z, normals[i].Y);
                //Vector2 t = new Vector2(texCoords[i].X, texCoords[i].Y);
                PositionNormalTexturedVertex pnv = new PositionNormalTexturedVertex(v, n, t);

                vertices.Write(pnv);
            }

            vertices.Position = 0;

            vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
                vertices,
                SizeOfVertexBufferInBytes,
                ResourceUsage.Default,
                BindFlags.VertexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None,
                0);
        }

        private void SetVertexBufferAndNormals(Assimp.Vector3D[] points, Assimp.Vector3D[] normals)
        {
            numVertices = points.Length;
            vertexStride = Marshal.SizeOf(typeof(PositionNormalVertex));
            int SizeOfVertexBufferInBytes = numVertices * vertexStride;

            vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);

            for (int i = 0; i < points.Length; i++)
            {
                Vector3 v = new Vector3(points[i].X, points[i].Y, points[i].Z);
                Vector3 n = new Vector3(normals[i].X, normals[i].Y, normals[i].Z);
                PositionNormalVertex pnv = new PositionNormalVertex(v, n);

                vertices.Write(pnv);
            }

            vertices.Position = 0;

            vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
                vertices,
                SizeOfVertexBufferInBytes,
                ResourceUsage.Default,
                BindFlags.VertexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None,
                0);
        }


        private void SetVertexBuffer(Assimp.Vector3D[] points)
        {
            numVertices = points.Length;
            vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
            int SizeOfVertexBufferInBytes = numVertices * vertexStride;

            vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);

            foreach (Assimp.Vector3D point in points)
            {
                Vector3 v = new Vector3(point.X, point.Y, point.Z);
                vertices.Write(v);
            }


            vertices.Position = 0;

            vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
                vertices,
                SizeOfVertexBufferInBytes,
                ResourceUsage.Default,
                BindFlags.VertexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None,
                0);
        }


        EffectWrapperTransformEffectWireframe ew = ShaderManager.Instance.transformEffectWireFrame;
        EffectWrapperPhongBlinn ews = ShaderManager.Instance.effectPhongBlinn;
        EffectWrapperPhongTexture ewt = ShaderManager.Instance.effectPhongTexture;

        public override void Render()
        {
            Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;

            Matrix WorldViewPerspective = this.transform * ViewPerspective;

            ew.tmat.SetMatrix(WorldViewPerspective);
            ew.mCol.Set(new Color4(1, ColorDiffuse.R / 255.0f, ColorDiffuse.G / 255.0f, ColorDiffuse.B / 255.0f));

            if (meshType == MeshType.VerticesOnly)
            {
                // configure the Input Assembler portion of the pipeline with the vertex data
                DeviceManager.Instance.context.InputAssembler.InputLayout = ew.layout;
                DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
                DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));

                ew.technique = ew.effect.GetTechniqueByName("Render");

                EffectTechniqueDescription techDesc;
                techDesc = ew.technique.Description;

                for (int p = 0; p < techDesc.PassCount; ++p)
                {
                    ew.technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
                    DeviceManager.Instance.context.Draw(numVertices, 0);
                }
            }

            if (meshType == MeshType.VertexNormalTextureCoord)
            {
                Vector3 viewDir = CameraManager.Instance.currentCamera.eye - CameraManager.Instance.currentCamera.target;

                ewt.World.SetMatrix(this.transform);
                //ewt.World.SetMatrix(Matrix.Scaling(-1, 1, 1) *  this.transform);
                ewt.View.SetMatrix(CameraManager.Instance.View);
                ewt.Projection.SetMatrix(CameraManager.Instance.currentCamera.Perspective);

                ewt.CameraPosition.Set(CameraManager.Instance.currentCamera.eye);
                ewt.LightDirection.Set(viewDir);

                ewt.DiffuseColor.Set(ColorToVector3(ColorDiffuse));
                ewt.AmbientColor.Set(ColorToVector3(ColorAmbient));
                ewt.SpecularColor.Set(ColorToVector3(ColorSpecular));
                ewt.LightColor.Set(new Vector3(1, 1, 1));

                ewt.SpecularPower.Set(128);

                DeviceManager.Instance.context.InputAssembler.InputLayout = ewt.layout;
                DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
                DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
                DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);

                if (textureFileName != null)
                {
                    ewt.TextureVariable.SetResource(TextureManager.Instance.GetShaderResourceView(textureFileName));
                }
                ewt.technique = ewt.effect.GetTechniqueByName("Render");

                EffectTechniqueDescription techDesc;
                techDesc = ewt.technique.Description;

                for (int p = 0; p < techDesc.PassCount; ++p)
                {
                    ewt.technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
                    DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
                }
            }

            if (meshType == MeshType.VertexNormalIndex)
            {
                Vector3 viewDir = CameraManager.Instance.currentCamera.eye - CameraManager.Instance.currentCamera.target;

                ews.World.SetMatrix(this.transform);
                ews.View.SetMatrix(CameraManager.Instance.View);
                ews.Projection.SetMatrix(CameraManager.Instance.currentCamera.Perspective);

                ews.CameraPosition.Set(CameraManager.Instance.currentCamera.eye);
                ews.LightDirection.Set(viewDir);

                ews.DiffuseColor.Set(ColorToVector3(ColorDiffuse));
                ews.AmbientColor.Set(ColorToVector3(ColorAmbient));
                ews.SpecularColor.Set(ColorToVector3(ColorSpecular));
                ews.LightColor.Set(new Vector3(1, 1, 1));

                //ews.DiffuseColor.Set(ColorToVector3(System.Drawing.Color.Red));
                //ews.SpecularColor.Set(ColorToVector3(System.Drawing.Color.White));








                DeviceManager.Instance.context.InputAssembler.InputLayout = ews.layout;
                DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
                DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
                DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);

                ews.technique = ews.effect.GetTechniqueByName("Render");

                EffectTechniqueDescription techDesc;
                techDesc = ews.technique.Description;

                for (int p = 0; p < techDesc.PassCount; ++p)
                {
                    ews.technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
                    DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
                }
            }



            if (meshType == MeshType.VertexAndIndexBuffer)
            {
                DeviceManager.Instance.context.InputAssembler.InputLayout = ew.layout;
                DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
                DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
                DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);

                ew.technique = ew.effect.GetTechniqueByName("Render");

                EffectTechniqueDescription techDesc;
                techDesc = ew.technique.Description;

                for (int p = 0; p < techDesc.PassCount; ++p)
                {
                    ew.technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
                    DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
                }

            }



        }

        public override void Dispose()
        {
            if (indexBuffer != null)
                indexBuffer.Dispose();

            if (vertexBuffer != null)
                vertexBuffer.Dispose();
        }

        public override SlimDX.Matrix Transform
        {
            get
            {
                return transform;
            }
            set
            {
                transform = value;
            }
        }
    }
}
